package scaladuct

import java.lang.reflect.{Type, Constructor}
import java.lang.{IllegalArgumentException, Class, String}

/**
 * <p>
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: Aug 18, 2010
 * Time: 2:34:32 PM
 */


object MapConfigPageLookup {

  class Pair(val instanceClass: Class[_], val starterClass: Class[_])

  implicit def apply[X, Z <: X, Y <: Starter[Z]](triple: (Class[X], Class[Y], Class[Z])): Pair = {
    val (instanceClass, starterClass, implClass) = triple
    new Pair(instanceClass.asInstanceOf[Class[_]], starterClass.asInstanceOf[Class[_]])
  }

  implicit def apply[X, Y <: Starter[X]](pair: (Class[X], Class[Y])): Pair = {
    val (instanceClass, starterClass) = pair
    new Pair(instanceClass.asInstanceOf[Class[_]], starterClass.asInstanceOf[Class[_]])
  }

  implicit def apply2[Y <: Starter[Y], X >: Y](pair: (Class[X], Class[Y])): Pair = {
    val (instanceClass, starterClass) = pair
    new Pair(instanceClass.asInstanceOf[Class[_]], starterClass.asInstanceOf[Class[_]])
  }

  implicit def apply3[Y <: Starter[Nothing]](starterClass: Class[Y]): Pair = {
    new Pair(classOf[Nothing].asInstanceOf[Class[_]], starterClass.asInstanceOf[Class[_]])
  }

  implicit def apply(pairs: Pair*): LookupFunctions.LookupFunction = {
    apply(List(pairs: _*))
  }

  implicit def apply(pairs: List[Pair]): LookupFunctions.LookupFunction = {
    new MapConfigPageLookup(Map(("" -> Map(pairs.map(pair => (pair.instanceClass, pair.starterClass)): _*))))
  }
}

@serializable
@SerialVersionUID(1L)
class MapConfigPageLookup(val lookupMap: Map[String, Map[Class[_], Class[_]]]) extends LookupFunctions.LookupFunction {

  private def instantiateConfigPage[X](pageClass: Class[ConfigPage[X]]): ConfigPage[X] = {
    val constructors: Array[Constructor[ConfigPage[X]]] =
      pageClass.getConstructors.asInstanceOf[Array[Constructor[ConfigPage[X]]]]

    require(constructors.length == 1, pageClass.getName +
      " page class must have just one constructor (" + constructors.length + ")")
    val parTypes: Array[Class[_]] = constructors(0).getParameterTypes
    parTypes.length match {
      case 0 => constructors(0).newInstance()
      case _ => throw new IllegalArgumentException("Unsupported number of arguments in config page's constructor: "
        + parTypes.length)
    }
  }

  private def instantiateStarter[X](refName: String, pageClass: Class[Starter[X]], args: AnyRef*): ConfigPage[X] = {
    PhaseMethodInterceptor[X](if (refName == "") "" else refName + ".", pageClass, args: _*)(this).phase.firstPage
  }

  def apply(refName: AnyRef): List[(Type, AnyRef *) => ConfigPage[_]] = resolveLookup(refName.toString)

  private def resolveLookup[X](refName: String): List[(Type, AnyRef *) => ConfigPage[X]] = {
    lookupMap.get(refName) match {
      case None => Nil
      case Some(starterMap) => {
        val pageFn: (Type, AnyRef *) => ConfigPage[X] = (resultType, args) => {

          if (!resultType.isInstanceOf[Class[_]]) {
            throw new IllegalArgumentException("Unsupported result type: " + resultType)
          } else {
            val resultClass = resultType.asInstanceOf[Class[_]]

            starterMap.get(resultClass) match {
              case None => throw new IllegalArgumentException("Neither starter nor config page found for instance class " + resultClass)
              case Some(pageClass) => if (classOf[Starter[X]].isAssignableFrom(pageClass))
                instantiateStarter[X](refName, pageClass.asInstanceOf[Class[Starter[X]]], args: _*)
              else if (classOf[ConfigPage[X]].isAssignableFrom(pageClass)) {
                instantiateConfigPage[X](pageClass.asInstanceOf[Class[ConfigPage[X]]])
              } else {
                throw new IllegalArgumentException("Page class is neither starter nor config page class:" + resultClass)
              }

            }
          }
        }
        List(pageFn)
      }
    }
  }

}
